// 
//    jSNMP - SNMPv1 & v2 Compliant Libraries for Java
//    Copyright (C) 2000  PlatformWorks, Inc.
//
//    This library is free software; you can redistribute it and/or
//    modify it under the terms of the GNU Lesser General Public
//    License as published by the Free Software Foundation; either
//    version 2.1 of the License, or (at your option) any later version.
//
//    This library is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//    Lesser General Public License for more details.
//
//    You should have received a copy of the GNU Lesser General Public
//    License along with this library; if not, write to the Free Software
//    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//   
// For more information contact: 
//  Brian Weaver      <weave@opennms.org>
//  http://www.opennms.org/
//
//  joeSNMP Licensing <joesnmp-license@lists.sourceforge.net>
//  http://sourceforge.net/projects/joesnmp/
//

package org.opennms.examples;

import java.io.InputStream;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.SortedMap;
import java.util.StringTokenizer;
import java.util.TreeMap;

import org.opennms.protocols.snmp.SnmpAgentHandler;
import org.opennms.protocols.snmp.SnmpAgentSession;
import org.opennms.protocols.snmp.SnmpEndOfMibView;
import org.opennms.protocols.snmp.SnmpInt32;
import org.opennms.protocols.snmp.SnmpOctetString;
import org.opennms.protocols.snmp.SnmpParameters;
import org.opennms.protocols.snmp.SnmpPduBulk;
import org.opennms.protocols.snmp.SnmpPduPacket;
import org.opennms.protocols.snmp.SnmpPduRequest;
import org.opennms.protocols.snmp.SnmpPeer;
import org.opennms.protocols.snmp.SnmpSMI;
import org.opennms.protocols.snmp.SnmpSyntax;
import org.opennms.protocols.snmp.SnmpVarBind;
import org.opennms.protocols.snmp.asn1.AsnEncodingException;


public class SimpleAgent implements SnmpAgentHandler
{
    private SnmpAgentSession m_session;
    private TreeMap m_mib = new TreeMap(new Comparator()
    {
        public int compare(Object o1, Object o2)
        {
            StringTokenizer stTokens1 = new StringTokenizer(o1.toString().trim(), ".");
            StringTokenizer stTokens2 = new StringTokenizer(o2.toString().trim(), ".");

            while (true)
            {
                if (stTokens1.hasMoreTokens())
                {
                    int value1 = Integer.parseInt(stTokens1.nextToken());

                    if (stTokens2.hasMoreTokens())
                    {
                        int value2 = Integer.parseInt(stTokens2.nextToken());

                        if (value1 != value2)
                            return value1 - value2;
                    }
                    else
                    {
                        return 1;
                    }
                }
                else if (stTokens2.hasMoreTokens())
                {
                    return 1;
                }
                else
                {
                    return 0;
                }
            }
        }

    });

    private static SimpleAgent ms_agent;

    private static final boolean DEBUG = false;

    SimpleAgent(String mibProperties) throws Exception
    {
        SnmpPeer peer = new SnmpPeer(InetAddress.getLocalHost(), 161);
        SnmpParameters params = peer.getParameters();
        params.setVersion(SnmpSMI.SNMPV2);
        params.setReadCommunity("public");
        params.setWriteCommunity("public");

        m_session = new SnmpAgentSession(this, peer);
        Properties pr = new Properties();
        InputStream input = SimpleAgent.class.getResourceAsStream(mibProperties);

        pr.load(input);
        input.close();

        for (Iterator i = pr.keySet().iterator(); i.hasNext();)
        {
            String oid = ((String) i.next()).trim();
            String value = pr.getProperty(oid).trim();

            if (value.startsWith("?"))
            {
                final long factor = value.equals("?") ? 1000 : Long.parseLong(value.substring(1));
                m_mib.put(oid, new SnmpInt32()
                {
                    private int counter;
                    private long lastSec = System.currentTimeMillis() / factor;

                    public SnmpSyntax duplicate()
                    {
                        SnmpInt32 dup = new SnmpInt32();
                        dup.setValue(getValue());
                        return dup;
                    }

                    public int getValue()
                    {
                        counter += System.currentTimeMillis() / factor - lastSec;
                        lastSec = System.currentTimeMillis() / factor;
                        return counter;
                    }
                });
            }
            else if (value.startsWith("#"))
            {
                StringTokenizer stTokens = new StringTokenizer(value.substring(1), ", ");
                final ArrayList list = new ArrayList();

                while (stTokens.hasMoreTokens())
                    list.add(new Integer(stTokens.nextToken()));

                m_mib.put(oid, new SnmpInt32()
                {
                    private int counter;

                    public SnmpSyntax duplicate()
                    {
                        SnmpInt32 dup = new SnmpInt32();
                        dup.setValue(getValue());
                        return dup;
                    }

                    public int getValue()
                    {
                        int value = ((Integer) list.get(counter)).intValue();
                        counter = ++counter % list.size();
                        return value;
                    }
                });
            }
            else
                m_mib.put(oid, new SnmpOctetString(value.getBytes()));
        }
    }

    public void SnmpAgentSessionError(SnmpAgentSession session, int error, Object ref)
    {
        new Exception().printStackTrace();
        System.exit(-1);
    }

    public void snmpReceivedPdu(SnmpAgentSession session, InetAddress manager, int port, SnmpOctetString community,
            SnmpPduPacket pdu)
    {
        if (pdu instanceof SnmpPduBulk)
        {
            SnmpPduRequest response = new SnmpPduRequest(SnmpPduRequest.RESPONSE);
            response.setRequestId(pdu.getRequestId());

            doBulk(pdu.toVarBindArray(), pdu, response);

            try
            {
                session.send(new SnmpPeer(manager, port), response);
            }
            catch (AsnEncodingException ex)
            {
                try
                {
                    response = new SnmpPduRequest(SnmpPduRequest.RESPONSE);
                    response.setRequestId(pdu.getRequestId());
                    response.setErrorStatus(SnmpPduRequest.ErrTooBig);
                    response.setErrorIndex(0);
                    session.send(new SnmpPeer(manager, port), response);
                }
                catch (Exception exp)
                {
                    exp.printStackTrace();
                }
            }
            catch (Exception ex)
            {
                ex.printStackTrace();
            }
        }
        else
        {
            if (DEBUG)
                System.out.println("snmpReceivedPdu was called (and ignored) -> " + manager + ", " + port + ", "
                        + community + ", " + pdu.getClass().getName());
        }
    }

    public SnmpPduRequest snmpReceivedGet(SnmpPduPacket pdu, boolean getNext)
    {
        if (DEBUG)
            System.out.println("snmpReceivedGet was called with -> " + pdu.getClass().getName() + ", getnext='"
                    + getNext + "'");
        SnmpVarBind[] binds = pdu.toVarBindArray();
        SnmpPduRequest response = new SnmpPduRequest(SnmpPduRequest.RESPONSE);

        response.setRequestId(pdu.getRequestId());

        if (binds.length > 20)
        {
            if (DEBUG)
                System.out.println(">> Responding with to Big -> " + pdu.getRequestId() + ", " + getOidList(binds));
            response.setErrorStatus(SnmpPduRequest.ErrTooBig);
            response.setErrorIndex(0);
            return response;
        }

        if (pdu instanceof SnmpPduRequest) // treat as V1
        {
            int errorIndex = 0;

            if (getNext)
            {
                if (DEBUG)
                    System.out
                            .println("snmpReceivedGet.getNext(" + pdu.getRequestId() + ", " + getOidList(binds) + ")");

                for (int i = 0; i < binds.length; i++)
                {
                    String oid = binds[i].getName().toString();
                    SortedMap map = m_mib.tailMap(oid + ".0");

                    if (!map.isEmpty())
                    {
                        oid = (String) map.firstKey();
                        response.addVarBind(new SnmpVarBind(oid, (SnmpSyntax) map.get(oid)));
                    }
                    else
                    {
                        response.addVarBind(new SnmpVarBind(oid));
                        errorIndex = i + 1;
                        break;
                    }
                }
            }
            else
            {
                if (DEBUG)
                    System.out.println("snmpReceivedGet.get(" + pdu.getRequestId() + ", " + getOidList(binds) + ")");

                for (int i = 0; i < binds.length; i++)
                {
                    String oid = binds[i].getName().toString();
                    SnmpSyntax value = (SnmpSyntax) m_mib.get(oid);

                    if (value != null)
                    {
                        response.addVarBind(new SnmpVarBind(oid, value));
                    }
                    else
                    {
                        response.addVarBind(new SnmpVarBind(oid));
                        errorIndex = i + 1;
                        break;
                    }
                }

            }

            if (errorIndex > 0)
            {
                if (DEBUG)
                    System.out.println("snmpReceivedGet.get/getNext '" + pdu.getRequestId() + "' index '" + errorIndex
                            + "' was not found");
                response.setErrorStatus(SnmpPduRequest.ErrNoSuchName);
                response.setErrorIndex(errorIndex);
            }
        }
        else if (pdu instanceof SnmpPduBulk) // Treat as V2
        {
            doBulk(binds, pdu, response);
        }
        else
        {
            if (DEBUG)
                System.out.println("snmpReceivedGet.unknownCMD");
            response.setErrorStatus(SnmpPduRequest.ErrGenError);
            response.setErrorIndex(1);
        }

        return response;
    }

    private void doBulk(SnmpVarBind binds[], SnmpPduPacket pdu, SnmpPduRequest response)
    {
        if (DEBUG)
            System.out.println("snmpReceivedGet.getBulk(" + pdu.getRequestId() + ", " + getOidList(binds) + ")");

        SnmpPduBulk bulk = (SnmpPduBulk) pdu;
        int nonRepeaters = bulk.getNonRepeaters();
        int maxRep = bulk.getMaxRepititions();

        for (int i = 0; i < Math.min(nonRepeaters, binds.length); i++)
        {
            String oid = binds[i].getName().toString();
            SortedMap map = m_mib.tailMap(oid + ".0");

            if (!map.isEmpty())
            {
                oid = (String) map.firstKey();
                response.addVarBind(new SnmpVarBind(oid, (SnmpSyntax) map.get(oid)));
            }
            else
            {
                response.addVarBind(new SnmpVarBind(oid, new SnmpEndOfMibView()));
            }
        }

        for (int i = nonRepeaters; i < binds.length; i++)
        {
            String oid = binds[i].getName().toString();
            SortedMap map = m_mib.tailMap(oid + ".0");

            if (!map.isEmpty())
            {
                Iterator it = map.keySet().iterator();

                for (int j = 0; j < maxRep; j++)
                {
                    if (!it.hasNext())
                    {
                        response.addVarBind(new SnmpVarBind(".1.9", new SnmpEndOfMibView()));
                        break;
                    }

                    oid = (String) it.next();
                    response.addVarBind(new SnmpVarBind(oid, (SnmpSyntax) map.get(oid)));
                }
            }
            else
            {
                response.addVarBind(new SnmpVarBind(oid, new SnmpEndOfMibView()));
            }
        }
    }

    private static List getOidList(SnmpVarBind[] binds)
    {
        List list = new ArrayList(binds.length);

        for (int i = 0; i < binds.length; i++)
            list.add(binds[i].getName());

        return list;
    }

    public SnmpPduRequest snmpReceivedSet(SnmpPduPacket pdu)
    {
        if (DEBUG)
            System.out.println("snmpReceivedSet was called with -> " + pdu.getClass().getName());
        SnmpVarBind[] binds = pdu.toVarBindArray();
        SnmpPduRequest response = new SnmpPduRequest(SnmpPduRequest.RESPONSE);
        int errorIndex = 0;

        response.setRequestId(pdu.getRequestId());

        if (binds.length > 20)
        {
            if (DEBUG)
                System.out.println(">> Responding with to Big -> " + pdu.getRequestId() + ", " + getOidList(binds));
            response.setErrorStatus(SnmpPduRequest.ErrTooBig);
            response.setErrorIndex(0);
            return response;
        }

        for (int i = 0; i < binds.length; i++)
        {
            String oid = binds[i].getName().toString();
            SnmpSyntax newValue = binds[i].getValue();
            SnmpSyntax value = (SnmpSyntax) m_mib.get(oid);

            if (DEBUG)
                System.out.println("oid value is -> " + oid + ", " + binds[i].getName());
            if (DEBUG)
                System.out.println("old value is -> " + value);
            if (DEBUG)
                System.out.println("New value is -> " + newValue);
            if (value != null)
            {
                m_mib.put(oid, newValue);
                response.addVarBind(new SnmpVarBind(oid, newValue));
            }
            else
            {
                response.addVarBind(new SnmpVarBind(oid));
                errorIndex = i + 1;
                break;
            }
        }

        if (errorIndex > 0)
        {
            if (DEBUG)
                System.out.println("snmpReceivedSet '" + pdu.getRequestId() + "' index '" + errorIndex
                        + "' was not found");
            response.setErrorStatus(SnmpPduRequest.ErrNoSuchName);
            response.setErrorIndex(errorIndex);
        }

        return response;
    }

    public static void startAgent(String mibProperties) throws Exception
    {
        stopAgent();
        System.out.println("Starting SNMP Agent on " + InetAddress.getLocalHost() + ":161 ....");
        ms_agent = new SimpleAgent(mibProperties);
    }

    public static void stopAgent()
    {
        if (ms_agent != null)
        {
            if (DEBUG)
                System.out.println("Stoping SNMP Agent....");
            
            ms_agent.m_session.close();
            ms_agent = null;
        }
    }

    public static void main(final String st[]) throws Exception
    {
        startAgent(st.length == 0 ? "agent-mib.properties" : st[0]);
        Thread.currentThread().join();
    }
}
